﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework.Resources;

namespace Microsoft.Xna.Framework
{
    /// <summary>
    /// 	Defines an axis-aligned rectangle-shaped 2D volume.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct BoundingRectangle : IEquatable<BoundingRectangle>
    {
        #region Constants

        /// <summary>
        /// 	Specifies the total number of corners (4) in the BoundingRectangle.
        /// </summary>
        public const int CornerCount = 4;

        #endregion

        #region Static Operations

        #region Create From

        /// <summary>
        /// 	Creates the smallest BoundingRectangle that will contain a group of points.
        /// </summary>
        /// <param name="points"> A list of points the BoundingRectangle should contain. </param>
        public static BoundingRectangle CreateFromPoints(IEnumerable<Vector2> points)
        {
            if (points == null)
            {
                throw new ArgumentNullException();
            }
            var hasPoint = false;
            var minPoint = new Vector2(float.MaxValue);
            var maxPoint = new Vector2(float.MinValue);
            foreach (var point in points)
            {
                var currentPoint = point;
                Vector2.Min(ref minPoint, ref currentPoint, out minPoint);
                Vector2.Max(ref maxPoint, ref currentPoint, out maxPoint);
                hasPoint = true;
            }
            if (!hasPoint)
            {
                throw new ArgumentException(Resx.BoundingRectangleZeroPoints);
            }
            return new BoundingRectangle(minPoint, maxPoint);
        }

        /// <summary>
        /// 	Creates the smallest BoundingRectangle that will contain the specified BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to contain. </param>
        public static BoundingRectangle CreateFromCircle(BoundingCircle circle)
        {
            BoundingRectangle rectangle;
            rectangle.Min.X = circle.Center.X - circle.Radius;
            rectangle.Min.Y = circle.Center.Y - circle.Radius;
            rectangle.Max.X = circle.Center.X + circle.Radius;
            rectangle.Max.Y = circle.Center.Y + circle.Radius;
            return rectangle;
        }

        /// <summary>
        /// 	Creates the smallest BoundingRectangle that will contain the specified BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to contain. </param>
        /// <param name="result"> [OutAttribute] The created BoundingRectangle. </param>
        public static void CreateFromCircle(ref BoundingCircle circle, out BoundingRectangle result)
        {
            result.Min.X = circle.Center.X - circle.Radius;
            result.Min.Y = circle.Center.Y - circle.Radius;
            result.Max.X = circle.Center.X + circle.Radius;
            result.Max.Y = circle.Center.Y + circle.Radius;
        }

        #endregion

        #region Create Merged

        /// <summary>
        /// 	Creates the smallest BoundingRectangle that contains the two specified BoundingRectangle instances.
        /// </summary>
        /// <param name="original"> One of the BoundingRectangle to contain. </param>
        /// <param name="additional"> One of the BoundingRectangle to contain. </param>
        public static BoundingRectangle CreateMerged(BoundingRectangle original, BoundingRectangle additional)
        {
            BoundingRectangle rectangle;
            Vector2.Min(ref original.Min, ref additional.Min, out rectangle.Min);
            Vector2.Max(ref original.Max, ref additional.Max, out rectangle.Max);
            return rectangle;
        }

        /// <summary>
        /// 	Creates the smallest BoundingRectangle that contains the two specified BoundingRectangle instances.
        /// </summary>
        /// <param name="original"> One of the BoundingRectangle instances to contain. </param>
        /// <param name="additional"> One of the BoundingRectangle instances to contain. </param>
        /// <param name="result"> [OutAttribute] The created BoundingRectangle. </param>
        public static void CreateMerged(ref BoundingRectangle original, ref BoundingRectangle additional, out BoundingRectangle result)
        {
            Vector2 maxPoint;
            Vector2 minPoint;
            Vector2.Min(ref original.Min, ref additional.Min, out minPoint);
            Vector2.Max(ref original.Max, ref additional.Max, out maxPoint);
            result.Min = minPoint;
            result.Max = maxPoint;
        }

        #endregion

        #region Equality & Inequality

        /// <summary>
        /// 	Returns a value that indicates whether two specified BoundingRectangle values are equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator ==(BoundingRectangle left, BoundingRectangle right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// 	Returns a value that indicates whether two specified BoundingRectangle values are not equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator !=(BoundingRectangle left, BoundingRectangle right)
        {
            if (left.Min == right.Min)
            {
                return left.Max != right.Max;
            }
            return true;
        }

        #endregion

        #endregion

        #region Fields & Properties

        /// <summary>
        /// 	The minimum point the BoundingRectangle contains.
        /// </summary>
        public Vector2 Min;

        /// <summary>
        /// 	The maximum point the BoundingRectangle contains.
        /// </summary>
        public Vector2 Max;

        #endregion

        #region Constructors

        /// <summary>
        /// 	Creates an instance of BoundingRectangle.
        /// </summary>
        /// <param name="min"> The minimum point the BoundingRectangle includes. </param>
        /// <param name="max"> The maximum point the BoundingRectangle includes. </param>
        public BoundingRectangle(Vector2 min, Vector2 max)
        {
            Min = min;
            Max = max;
        }

        #endregion

        #region Implementation of IEquatable<BoundingRectangle>

        /// <summary>
        /// 	Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns> true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false. </returns>
        /// <param name="other"> An object to compare with this object. </param>
        public bool Equals(BoundingRectangle other)
        {
            return Min == other.Min && Max == other.Max;
        }

        #endregion

        #region Overrides of ValueType

        /// <summary>
        /// 	Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns> true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
        /// <param name="obj"> Another object to compare to. </param>
        public override bool Equals(object obj)
        {
            var result = false;
            if (obj != null && obj is BoundingRectangle)
            {
                result = Equals((BoundingRectangle) obj);
            }
            return result;
        }

        /// <summary>
        /// 	Returns the hash code for this instance.
        /// </summary>
        /// <returns> A 32-bit signed integer that is the hash code for this instance. </returns>
        public override int GetHashCode()
        {
            return Min.GetHashCode() + Max.GetHashCode();
        }

        /// <summary>
        /// 	Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns> A <see cref="T:System.String" /> containing a fully qualified type name. </returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.CurrentCulture, "{{Min:{0} Max:{1}}}", Min, Max);
        }

        #endregion

        #region Get Corners

        /// <summary>
        /// 	Gets an arline of points that make up the corners of the BoundingRectangle.
        /// </summary>
        public Vector2[] GetCorners()
        {
            return new[]
                       {
                           new Vector2(Min.X, Max.Y), new Vector2(Max.X, Max.Y), new Vector2(Max.X, Min.Y), new Vector2(Min.X, Min.Y)
                       };
        }

        /// <summary>
        /// 	Gets the arline of points that make up the corners of the BoundingRectangle.
        /// </summary>
        /// <param name="corners"> An existing arline of at least 8 Vector2 points where the corners of the BoundingRectangle are written. </param>
        public void GetCorners(Vector2[] corners)
        {
            if (corners == null)
            {
                throw new ArgumentNullException("corners");
            }
            if (corners.Length < CornerCount)
            {
                throw new ArgumentOutOfRangeException("corners", Resx.NotEnoughCorners);
            }
            corners[0x0].X = Min.X;
            corners[0x0].Y = Max.Y;
            corners[0x1].X = Max.X;
            corners[0x1].Y = Max.Y;
            corners[0x2].X = Max.X;
            corners[0x2].Y = Min.Y;
            corners[0x3].X = Min.X;
            corners[0x3].Y = Min.Y;
        }

        #endregion

        #region Contains

        /// <summary>
        /// 	Tests whether the BoundingRectangle contains a point.
        /// </summary>
        /// <param name="point"> The point to test for overlap. </param>
        public ContainmentType Contains(Vector2 point)
        {
            if (point.X >= Min.X && point.X <= Max.X && point.Y >= Min.Y && point.Y <= Max.Y)
            {
                return ContainmentType.Contains;
            }
            return ContainmentType.Disjoint;
        }

        /// <summary>
        /// 	Tests whether the BoundingRectangle contains a point.
        /// </summary>
        /// <param name="point"> The point to test for overlap. </param>
        /// <param name="result"> [OutAttribute] Enumeration indicating the extent of overlap. </param>
        public void Contains(ref Vector2 point, out ContainmentType result)
        {
            result = (point.X >= Min.X && point.X <= Max.X && point.Y >= Min.Y && point.Y <= Max.Y) ? ContainmentType.Contains : ContainmentType.Disjoint;
        }

        /// <summary>
        /// 	Tests whether the BoundingRectangle contains another BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to test for overlap. </param>
        public ContainmentType Contains(BoundingRectangle rectangle)
        {
            if (Max.X < rectangle.Min.X || Min.X > rectangle.Max.X)
            {
                return ContainmentType.Disjoint;
            }
            if (Max.Y < rectangle.Min.Y || Min.Y > rectangle.Max.Y)
            {
                return ContainmentType.Disjoint;
            }
            if (rectangle.Min.X >= Min.X && rectangle.Max.X <= Max.X && rectangle.Min.Y >= Min.Y && rectangle.Max.Y <= Max.Y)
            {
                return ContainmentType.Contains;
            }
            return ContainmentType.Intersects;
        }

        /// <summary>
        /// 	Tests whether the BoundingRectangle contains a BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to test for overlap. </param>
        /// <param name="result"> [OutAttribute] Enumeration indicating the extent of overlap. </param>
        public void Contains(ref BoundingRectangle rectangle, out ContainmentType result)
        {
            result = ContainmentType.Disjoint;
            if (rectangle.Min.X <= Max.X && rectangle.Max.X >= Min.X && rectangle.Min.Y <= Max.Y && rectangle.Max.Y >= Min.Y)
            {
                result = (rectangle.Min.X >= Min.X && rectangle.Max.X <= Max.X && rectangle.Min.Y >= Min.Y && rectangle.Max.Y <= Max.Y) ? ContainmentType.Contains : ContainmentType.Intersects;
            }
        }

        /// <summary>
        /// 	Tests whether the BoundingRectangle contains a BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to test for overlap. </param>
        public ContainmentType Contains(BoundingCircle circle)
        {
            Vector2 closestPoint;
            Vector2.Clamp(ref circle.Center, ref Min, ref Max, out closestPoint);
            float closestDistanceSquared;
            Vector2.DistanceSquared(ref circle.Center, ref closestPoint, out closestDistanceSquared);
            var radius = circle.Radius;
            if (closestDistanceSquared > (radius * radius))
            {
                return ContainmentType.Disjoint;
            }
            if (circle.Center.X >= (Min.X + radius) && circle.Center.X <= (Max.X - radius) && (Max.X - Min.X) > radius && circle.Center.Y >= (Min.Y + radius) && circle.Center.Y <= (Max.Y - radius) && (Max.Y - Min.Y) > radius)
            {
                return ContainmentType.Contains;
            }
            return ContainmentType.Intersects;
        }

        /// <summary>
        /// 	Tests whether the BoundingRectangle contains a BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to test for overlap. </param>
        /// <param name="result"> [OutAttribute] Enumeration indicating the extent of overlap. </param>
        public void Contains(ref BoundingCircle circle, out ContainmentType result)
        {
            Vector2 closestPoint;
            Vector2.Clamp(ref circle.Center, ref Min, ref Max, out closestPoint);
            float closestDistanceSquared;
            Vector2.DistanceSquared(ref circle.Center, ref closestPoint, out closestDistanceSquared);
            var radius = circle.Radius;
            if (closestDistanceSquared > (radius * radius))
            {
                result = ContainmentType.Disjoint;
            }
            else
            {
                result = (circle.Center.X >= (Min.X + radius) && circle.Center.X <= (Max.X - radius) && (Max.X - Min.X) > radius && circle.Center.Y >= (Min.Y + radius) && circle.Center.Y <= (Max.Y - radius) && (Max.Y - Min.Y) > radius) ? ContainmentType.Contains : ContainmentType.Intersects;
            }
        }

        #endregion

        #region Intersects

        /// <summary>
        /// 	Checks whether the current BoundingRectangle intersects another BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to check for intersection with. </param>
        public bool Intersects(BoundingRectangle rectangle)
        {
            if (rectangle.Min.X > Max.X || rectangle.Max.X < Min.X)
            {
                return false;
            }
            if (rectangle.Min.Y > Max.Y || rectangle.Max.Y < Min.Y)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 	Checks whether the current BoundingRectangle intersects another BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] true if the BoundingRectangle instances intersect; false otherwise. </param>
        public void Intersects(ref BoundingRectangle rectangle, out bool result)
        {
            result = false;
            if (rectangle.Min.X <= Max.X && rectangle.Max.X >= Min.X && rectangle.Min.Y <= Max.Y && rectangle.Max.Y >= Min.Y)
            {
                result = true;
            }
        }

        /// <summary>
        /// 	Checks whether the current BoundingRectangle intersects a BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to check for intersection with. </param>
        public bool Intersects(BoundingCircle circle)
        {
            Vector2 closestPoint;
            Vector2.Clamp(ref circle.Center, ref Min, ref Max, out closestPoint);
            float closestDistanceSquared;
            Vector2.DistanceSquared(ref circle.Center, ref closestPoint, out closestDistanceSquared);
            return (closestDistanceSquared <= (circle.Radius * circle.Radius));
        }

        /// <summary>
        /// 	Checks whether the current BoundingRectangle intersects a BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] true if the BoundingRectangle and BoundingCircle intersect; false otherwise. </param>
        public void Intersects(ref BoundingCircle circle, out bool result)
        {
            Vector2 closestPoint;
            Vector2.Clamp(ref circle.Center, ref Min, ref Max, out closestPoint);
            float closestDistanceSquared;
            Vector2.DistanceSquared(ref circle.Center, ref closestPoint, out closestDistanceSquared);
            result = closestDistanceSquared <= (circle.Radius * circle.Radius);
        }

        /// <summary>
        /// 	Checks whether the current BoundingRectangle intersects a Line.
        /// </summary>
        /// <param name="line"> The Line to check for intersection with. </param>
        public float? Intersects(Rayline line)
        {
            var distance = 0f;
            var maxValue = float.MaxValue;
            if (Math.Abs(line.Direction.X) < 1E-06f)
            {
                if (line.Position.X < Min.X || line.Position.X > Max.X)
                {
                    return null;
                }
            }
            else
            {
                var factorX = 1f / line.Direction.X;
                var minDistanceX = (Min.X - line.Position.X) * factorX;
                var maxDistanceX = (Max.X - line.Position.X) * factorX;
                if (minDistanceX > maxDistanceX)
                {
                    var swap = minDistanceX;
                    minDistanceX = maxDistanceX;
                    maxDistanceX = swap;
                }
                distance = MathHelper.Max(minDistanceX, distance);
                maxValue = MathHelper.Min(maxDistanceX, maxValue);
                if (distance > maxValue)
                {
                    return null;
                }
            }
            if (Math.Abs(line.Direction.Y) < 1E-06f)
            {
                if (line.Position.Y < Min.Y || line.Position.Y > Max.Y)
                {
                    return null;
                }
            }
            else
            {
                var factorY = 1f / line.Direction.Y;
                var minDistanceY = (Min.Y - line.Position.Y) * factorY;
                var maxDistanceY = (Max.Y - line.Position.Y) * factorY;
                if (minDistanceY > maxDistanceY)
                {
                    var swap = minDistanceY;
                    minDistanceY = maxDistanceY;
                    maxDistanceY = swap;
                }
                distance = MathHelper.Max(minDistanceY, distance);
                maxValue = MathHelper.Min(maxDistanceY, maxValue);
                if (distance > maxValue)
                {
                    return null;
                }
            }
            return distance;
        }

        /// <summary>
        /// 	Checks whether the current BoundingRectangle intersects a Line.
        /// </summary>
        /// <param name="line"> The Line to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] Distance at which the line intersects the BoundingRectangle, or null if there is no intersection. </param>
        public void Intersects(ref Rayline line, out float? result)
        {
            result = 0x0;
            var distance = 0f;
            var maxValue = float.MaxValue;
            if (Math.Abs(line.Direction.X) < 1E-06f)
            {
                if (line.Position.X < Min.X || line.Position.X > Max.X)
                {
                    return;
                }
            }
            else
            {
                var factorX = 1f / line.Direction.X;
                var minDistanceX = (Min.X - line.Position.X) * factorX;
                var maxDistanceX = (Max.X - line.Position.X) * factorX;
                if (minDistanceX > maxDistanceX)
                {
                    var swap = minDistanceX;
                    minDistanceX = maxDistanceX;
                    maxDistanceX = swap;
                }
                distance = MathHelper.Max(minDistanceX, distance);
                maxValue = MathHelper.Min(maxDistanceX, maxValue);
                if (distance > maxValue)
                {
                    return;
                }
            }
            if (Math.Abs(line.Direction.Y) < 1E-06f)
            {
                if (line.Position.Y < Min.Y || line.Position.Y > Max.Y)
                {
                    return;
                }
            }
            else
            {
                var factorY = 1f / line.Direction.Y;
                var minDistanceY = (Min.Y - line.Position.Y) * factorY;
                var maxDistanceY = (Max.Y - line.Position.Y) * factorY;
                if (minDistanceY > maxDistanceY)
                {
                    var swap = minDistanceY;
                    minDistanceY = maxDistanceY;
                    maxDistanceY = swap;
                }
                distance = MathHelper.Max(minDistanceY, distance);
                maxValue = MathHelper.Min(maxDistanceY, maxValue);
                if (distance > maxValue)
                {
                    return;
                }
            }
            result = distance;
        }

        #endregion
    }
}